

function KalObject(TYPE,MODULE) {
    this.members = {"class":TYPE,"module":MODULE};
}

function KalModule(URL) {
    this.members["URL"] = URL;
    this.members["module"] = this;
    this.members["globals"] = this.members;
    //if (URL == "$kal_core") {
    //    this.func = function () {};
    //    return;
    //}
    var conn;
    if (XMLHttpRequest)
      {// code for IE7+, Firefox, Chrome, Opera, Safari
      conn=new XMLHttpRequest();
      }
    else
      {// code for IE6, IE5
      conn=new ActiveXObject("Microsoft.XMLHTTP");
      }
      globals_stack.push(globals);
      globals = this.members;
      locals_stack.push(locals);
      locals = globals;
      code_obj_stack.push(code_obj);
      code_obj = this;
      conn.open("GET",URL,false);
      conn.send();
      var js_code = _KalParseFile(KalDecode64(conn.responseText));
      try {
      this.func = eval(js_code[1]);
        } catch (e) {
            print(js_code)
        }
      this.func();
      code_obj = code_obj_stack.pop();
      globals = globals_stack.pop();
      locals = locals_stack.pop();
}
KalModule.prototype = new KalObject(null,null);

function KalClass(cls, parents) {
    this.members["parents"] = [];
    //Run all parent class class initializers
    for (var i = 0; i < parents.length; i++) {
        //call the initializer
        code_obj_stack.push(code_obj);
        var parent = _GET_VAR(parents[i]);
        code_obj = parent;
        locals_stack.push(locals);
        
        locals = this.members;
        
        code_obj.func(); //call
        
        code_obj = code_obj_stack.pop();
        locals = locals_stack.pop();
        
        //append the parent class to the .parents member
        this.members["parents"].push(parent);
    }
    //call the initializer for this class
    code_obj_stack.push(code_obj);
    code_obj = this;
    locals_stack.push(locals);
    
    locals = this.members;
    this.func = cls;
    code_obj.func();
    
    code_obj = code_obj_stack.pop();
    locals = locals_stack.pop();

}
KalClass.prototype = new KalObject(null,null);

var stack = [];
var globals = null;
var locals = globals;
var locals_stack = [];
var globals_stack = [];
var iter_stack = []
var iter = null;
var code_obj;
var code_obj_stack = [];

var KalCoreModule = new KalModule("runtime.kalc");
globals = KalCoreModule.members;
code_obj = KalCoreModule;

KalClass.prototype = new KalObject(KalCoreModule.members["Object"],KalCoreModule);
KalModule.prototype = new KalObject(KalCoreModule.members["Module"]);
KalCoreModule.members["class"] = KalCoreModule.members["Module"];

function KalFunction(func, args, module) {
    this.func = func;
    this.members["bound_to"] = KalCoreModule.members["None"]
    this.members["arguments"] = args;
    this.members["module"] = module;
}
KalFunction.prototype = new KalObject(KalObject,KalCoreModule);

function KalBoundFunction(obj, func) {
    this.func = func.func;
    this.members = {}
    for (var m in func.members) {
        this.members[m] = func.members[m];
    }
    this.members["bound_to"] = obj;
}
KalBoundFunction.prototype = new KalFunction();

function KalBoolean(val) {
    this.v = val;
}
KalBoolean.prototype = new KalObject(KalObject,KalCoreModule);
var KalTrue = new KalBoolean(true);
var KalFalse = new KalBoolean(false);

function KalNumber(val) {
    this.v = val
}

function _RAISE(EXCEPTION,MESSAGE) {
    throw EXCEPTION + " - " + MESSAGE;
    return -1;
}

function _GET_VAR(VAR_NAME) {
    var v;
    v = locals[VAR_NAME];
    if (v != undefined) {
        return v;
    }
    /*var self = locals["self"];
    if (self) {
        v = self.members[VAR_NAME];
        if (v != undefined) {
            return v;
        }
        return _GET_DEFAULT(self.members["class"], VAR_NAME);
    }*/
    v = globals[VAR_NAME];
    if (v != undefined) {
        return v;
    }
    v = KalCoreModule.members[VAR_NAME];
    if (v != undefined) {
        return v;
    }
    return null;
}

function _GET_DEFAULT(CLASS, VAR_NAME) {
    var v = CLASS.members[VAR_NAME];
    if (v != undefined) {
        return v;
    }
    var parents = CLASS.members["mro"];
    if (parents instanceof Array) {
        for (var i = 0; i < parents.length; i++) {
            v = _GET_DEFAULT(parents[i], VAR_NAME);
            if (v != null) {
                return v;
            }
        }
    }
    return null;
}

function _IS_A(CLASS1,CLASS2) {
    if (CLASS1 == CLASS2) {
        return true;
    } else {
        var parents = CLASS1.members["parents"];
        if (parents instanceof Array) {
            for (var i = 0; i < parents.length; i++) {
                if (_IS_A(parents[i],CLASS2)) return true;
            }
        }
    }
    return false;
}

function _SET_VAR(VAR_NAME, VALUE) {
    //For now go the python route of explicit global writes only
    /*if (locals[VAR_NAME] != undefined) {
        locals[VAR_NAME] = VALUE;
    } else {
        var self = locals["self"];
        if (self) {
            if (self.members[VAR_NAME] != undefined) {
                self.members[VAR_NAME] = VALUE;
            } else if (_GET_DEFAULT(CLASS.members["class"], VAR_NAME) != undefined) {
                self.members[VAR_NAME] = VALUE;
            }
        } else if (globals[VAR_NAME]) {
           globals[VAR_NAME] = VALUE;
        }
    }
    */
    locals[VAR_NAME] = VALUE;
}

function NO_OP() {};
function IMPORT(NAME) {
    stack.push(new KalModule(NAME+'.kalc'));
};
function SYS_CALL(CALL) {
    eval(CALL);
};
function BREAKPOINT() {};
function PRINT() {
    console.log(stack.pop());
};
function PUSH_CLASS(CLASS,PARENTS) {
    stack.push(new KalClass(CLASS,PARENTS));
};
function PUSH_FUNCTION(FUNCTION,ARGS) {
    stack.push(new KalFunction(FUNCTION,ARGS,code_obj.members["module"]));
};
function PUSH_PROPERTY(PROPERTY_NAME) {};
function PUSH_VAR(VAR_NAME) {
    var v = _GET_VAR(VAR_NAME);
    if (v != null) {
        stack.push(v);
    } else {
        _DISP_OBJ(locals)
        _DISP_OBJ(globals)
        return _RAISE("NameError","member or variable '" + VAR_NAME + "' could not be found");
    }
};
function PUSH_CONST(CONST) {
    stack.push(CONST);
};
function POP(DEST_VAR_NAME) {
    var v = stack.pop();
    if (DEST_VAR_NAME) {
        _SET_VAR(DEST_VAR_NAME, v)
    }
};
function DUPLICATE() {
    stack.push(stack[stack.length - 1]);
};
function SWAP() {
    var t1 = stack.pop();
    var t2 = stack.pop();
    stack.push(t1);
    stack.push(t2);
};
function PUSH_ITER() {
    if (GET_MEMBER("get_iterator") == -1) return -1;
    if (CALL(0) == -1) return -1;
    if (GET_MEMBER("next_item") == -1) return -1;
    iter_stack.push(iter);
    iter = stack.pop();
    
};
function POP_ITER() {
    iter = iter_stack.pop();
};
function NEXT_ITER(ITER_VAR) {
    stack.push(iter);
    if (CALL(0) == -1) return -1;
    locals[ITER_VAR] = stack.pop();
    if (locals[ITER_VAR] == KalCoreModule.members["StopIteration"]) {
        iter = iter_stack.pop();
        return true;
    }
    return false;
};

function ITERATE() {
    
}
function IS_A() {
    var v = stack.pop();
    var c = stack.pop();
    if (v instanceof KalObject) {
        stack.push(_IS_A(v.members["class"],c))
    } else {
        return _RAISE("NotImplementedError", "IS_A NOT IMPLEMENTED FOR PRIMITIVES")
    }
};
function PUSH_LOCALS() {
    locals_stack.push(locals);
    locals = [];
};
function POP_LOCALS() {
    locals = locals_stack.pop();
};
function JUMP_TO_OBJECT() {};
function JUMP_IF_TRUE() {
    return stack.pop() ? false : true;
};
function JUMP_IF_FALSE() {
    return stack.pop() ? true : false;
};
function JUMP(OFFSET) {};
function CALL(ARG_COUNT) {
    var f = stack.pop();
    if (f instanceof KalFunction) {
        
    } else if (f.members["call"] instanceof KalFunction) {
        f = f.members["call"];
    } else {
        if (varg) {
            stack.pop();
        } else {
            for (var i = 0; i < ARG_COUNT; i++) {
                stack.pop();
            }
        }
        return _RAISE("TypeError","object is not callable");
    }

    var args = f.members["arguments"];
    var argc = args.length;
    var varg = (args[argc-1] == "*") ? true : false;
    if (varg && argc) { //decrement argc if it isn't already 0 and we have a "*" argument
        argc -= 1;
    }
    if (ARG_COUNT == argc || (ARG_COUNT >= argc && varg)) {
        code_obj_stack.push(code_obj);
        code_obj = f;
        locals_stack.push(locals);
        locals = {};
        if (f.members["bound_to"] != KalCoreModule.members["None"]) {
            locals["self"] = f.members["bound_to"];
        }
        if (ARG_COUNT == -1) { //passed as *vargs
            varg_array = stack.pop();
            for (var i = 0; i < argc; i++) {
                locals[args[i]] = varg_array[i];
            }
            locals["arguments"] = varg_array;
        } else { //passed on stack
            locals["arguments"] = [];
            for (var i = 0; i < ARG_COUNT; i++) {
                var v = stack.pop();
                locals["arguments"].push(v);
                if (i < argc) {
                    locals[args[i]] = v;
                }
            }
        }
        
        globals_stack.push(globals);
        
        globals = code_obj.members["module"].members;
        code_obj.func();
        code_obj = code_obj_stack.pop();
        locals = locals_stack.pop();
        globals = globals_stack.pop();
    } else {
        if (varg) {
            stack.pop();
            return _RAISE("ArgumentError","expected at least " + (argc-1) + " arguments, but received " + ARG_COUNT);
        } else {
            for (var i = 0; i < ARG_COUNT; i++) {
                stack.pop();
            }
            return _RAISE("ArgumentError","expected " + argc + " arguments, but received " + ARG_COUNT);
        }
    }
};
function CALL_MEMBER(MEMBER_NAME,ARG_COUNT) {};
function RETURN() {
 
};
function CALL_RETURN() {
    //POP_LOCALS();
    RETURN();
};
function MAKE_LIST(LENGTH) {
    var l = [];
    for (var x = LENGTH; x > 0; x--) {
        l.push(stack.pop());
    }
    stack.push(l);
};
function MAKE_MAP(LENGTH) {
    var m = {};
    for (var x = LENGTH; x > 0; x--) {
        var k = stack.pop();
        var v = stack.pop();
        m[k] = v;
    }
    stack.push(m);
};
function CONCATENATE(LENGTH) {
    MAKE_LIST(LENGTH);
    stack.push(stack.pop().join(""));
};
function SPLIT() {
    var l = stack.pop();
    var spl = stack.pop();
    stack.push(l.split(spl));
};
function JOIN() {
    var l = stack.pop();
    var j = stack.pop();
    stack.push(l.join(j));
};
function LENGTH() {
    stack.push(stack.pop().length);
};
function ADD() {
    stack.push(stack.pop() + stack.pop());
};
function SUBTRACT() {
    stack.push(stack.pop() - stack.pop());
};
function MULTIPLY() {
    stack.push(stack.pop() * stack.pop());
};
function DIVIDE() {
    stack.push(stack.pop() / stack.pop());
};
function MODULUS() {
    stack.push(stack.pop() % stack.pop());
};
function EXPONENT() {
    stack.push(Math.pow(stack.pop(),stack.pop()));
};
function BIT_AND() {
    stack.push(stack.pop() & stack.pop());
};
function BIT_OR() {
    stack.push(stack.pop() | stack.pop());
};
function BIT_XOR() {
    stack.push(stack.pop() ^ stack.pop());
};
function PUSH_CODE_OBJ() {
    stack.push(code_obj);
};
//function PUSH_PC_OFFSET() {};
function PUSH_STACK_LENGTH() {
    stack.push(stack.length);
};
function STR_CMP() {
    stack.push(stack.pop().localeCompare(stack.pop()));
};
function ORDINAL() {};
function CHARACTER() {};
function EQUALS() {
    stack.push(stack.pop() == stack.pop());
};
function GREATER_THAN() {
    stack.push(stack.pop() > stack.pop());
};
function LESS_THAN() {
    stack.push(stack.pop() < stack.pop());
};
function GREATER_EQUAL() {
    stack.push(stack.pop() >= stack.pop());
};
function LESS_EQUAL() {
    stack.push(stack.pop() <= stack.pop());
};
function GET_LIST_ITEM() {
    var l = stack.pop();
    stack.push(l[stack.pop()]);
};
function SET_LIST_ITEM() {
    var v = stack.pop()
    var l = stack.pop();
    l[stack.pop()] = v;
};
function GET_LIST_SLICE() {};
function GET_MAP_ITEM() {};
function SET_MAP_ITEM() {};
function GET_STRING_ITEM() {};
function SET_STRING_ITEM() {};
function GET_STRING_SLICE() {};
function ALLOC() {
    //for now
  var o = new KalObject(stack.pop(),code_obj.members["module"]);
  stack.push(o);
};
function GET_ID() {};
function ID_EQUALS() {};
function HAS_MEMBER() {};
function GET_MEMBER(NAME) {
    var v = stack.pop();
    var member;
    if (v instanceof KalObject) {
        if (v.members[NAME] != undefined) {
            member = v.members[NAME];
        } else {
            member = _GET_DEFAULT(v.members["class"],NAME);
            if (member == null) {
                return _RAISE("AttributeError", "object does not have a property '" + NAME + "'");
            }
            if ((member instanceof KalFunction) && !(member instanceof KalBoundFunction)) {
                member = new KalBoundFunction(v,member);
                v.members[NAME] = member; //cache it
            }
        }
    } else {
        return _RAISE("NotImplementedError", "PRIMITIVE PROPERTIES NOT IMPLEMENTED");
    }
    stack.push(member);
};
function SET_MEMBER(NAME) {
    var o = stack.pop();
    var v = stack.pop();
    if (o instanceof KalObject) {
        o.members[NAME] = v;
    } else {
        return _RAISE("NotImplementedError", "PRIMITIVE PROPERTIES NOT IMPLEMENTED");
    }
};
function LIST_APPEND() {};
function LIST_EXTEND() {};
function LIST_REMOVE() {};
function FORMAT_INT() {};
function FORMAT_FLOAT() {};
function ASSERT(AND_RAISE) {

    if (stack.pop() != KalCoreModule.members["True"]) {
        var URL;
        try {
            URL = code_obj_stack[1].members["URL"];
        } catch (e) {
            URL = "unknown";
        }
        console.error("Assertion Failed - Module " + URL);
        if (AND_RAISE) {
            return _RAISE("AssertionFailedError", "Assertion Failed - Module " + URL);
        }
    }
};
function STOP() {
    throw "kal stopping execution"
};

//<DEBUG>
function _ASSERT1(NAME) {
    var v1 = stack.pop();
    if (v1 != KalCoreModule.members["True"]) {
        console.error(" assertion failed for condition " + NAME)
    }
}
function _ASSERT2(NAME) {
    var v1 = stack.pop();
    var v2 = stack.pop();
    if (v1 != v2) {
        console.error(" assertion failed for condition " + NAME)
        console.error("  expected: " + v2);
        console.error("       got: " + v1);
        console.error("Stack:");
        _DISP_OBJ(stack);
        console.error("Locals:");
        _DISP_OBJ(locals);
        console.error("Globals");
        _DISP_OBJ(globals);
    }
}
function _DISP_OBJ(o) {
    for (var i in o) {
        console.log(i + ': ' + o[i] + ',');
    }
}
function _STACK_CHECK() {
    var URL;
    try {
        URL = code_obj_stack[1].members["URL"];
    } catch (e) {
        URL = "unknown";
    }
    if (stack.length > 0) {
        console.error("Check Failed - Module " + URL + " - stack " + stack.length);
    }
    if (locals_stack.length > 1) {
        console.error("Check Failed - Module " + URL + " - locals_stack " + locals_stack.length);
    }
    if (globals_stack.length > 1) {
        console.error("Check Failed - Module " + URL + " - globals_stack " + globals_stack.length);
    }
    if (code_obj_stack.length > 1) {
        console.error("Check Failed - Module " + URL + " - code_obj_stack " + code_obj_stack.length);
    }
    if (iter_stack.length > 0) {
        console.error("Check Failed - Module " + URL + " - iter_stack " + iter_stack.length);
    }
}
//</DEBUG>
